﻿namespace Blaze.ConfigExportTool.Serialization
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;

    /// <summary>
    /// 提供将对象序列化到流的功能。
    /// </summary>
    public abstract class SerializerBase
    {
        protected class Indentation : IDisposable
        {
            #region IDisposable Members

            /// <summary>
            /// 执行与释放或重置非托管资源相关的应用程序定义的任务。
            /// </summary>
            public void Dispose()
            {
                mSerializer.mIndentLevel--;
                mSerializer.WriteIndentation(mSerializer.mIndentLevel);
            }

            #endregion

            public Indentation(SerializerBase serializer)
            {
                mSerializer = serializer;
                mSerializer.mIndentLevel++;
                mSerializer.WriteIndentation(mSerializer.mIndentLevel);
            }

            private readonly SerializerBase mSerializer;
        }

        protected class Block : IDisposable
        {
            #region IDisposable Members

            /// <summary>
            /// 执行与释放或重置非托管资源相关的应用程序定义的任务。
            /// </summary>
            public void Dispose()
            {
                if (mNewLine)
                {
                    mSerializer.WriteLine();
                    mIndentation.Dispose();
                }
                mSerializer.Writer.Write(mEnd);
            }

            #endregion

            public Block(SerializerBase serialzier, string begin, string end, bool newLine = true)
            {
                mSerializer = serialzier;
                mNewLine = newLine;
                mEnd = end;
                mSerializer.Writer.Write(begin);
                if (newLine)
                {
                    mSerializer.WriteLine();
                    mIndentation = new Indentation(mSerializer);
                }
            }

            private readonly string mEnd;
            private readonly Indentation mIndentation;
            private readonly bool mNewLine;
            private readonly SerializerBase mSerializer;
        }

        /// <summary>
        /// 获取需要序列化的目标类型。
        /// </summary>
        protected Type TargetType { get; private set; }

        /// <summary>
        /// 获取当前输出流所对应的写入器。
        /// </summary>
        protected StreamWriter Writer { get; private set; }

        /// <summary>
        /// 构造一个序列化器，并指定需要序列化的类型。
        /// </summary>
        /// <param name="targetType">目标类型</param>
        protected SerializerBase(Type targetType)
        {
            TargetType = targetType;
        }

        /// <summary>
        /// 将对象以指定的方式序列化到流中。
        /// </summary>
        /// <param name="stream">输出流</param>
        /// <param name="obj">需要序列化的对象</param>
        public void Serialize(Stream stream, object obj)
        {
            if (!TargetType.IsInstanceOfType(obj))
                throw new InvalidCastException();

            Writer = new StreamWriter(stream, new UTF8Encoding(false));
            OnSerialize(obj);
            Writer.Flush();
        }

        /// <summary>
        /// 获取一个值，表示指定字段是否将被序列化为Map。
        /// </summary>
        protected static bool IsDataMap(FieldInfo fieldInfo)
        {
            if (fieldInfo == null)
                return false;
            return fieldInfo.GetCustomAttribute<DataMapAttribute>() != null;
        }

        /// <summary>
        /// 获取一个值，表示指定字段是否将被序列化为MapKey。
        /// </summary>
        protected static bool IsDataMapKey(FieldInfo fieldInfo)
        {
            if (fieldInfo == null)
                return false;
            return fieldInfo.GetCustomAttribute<DataMapKeyAttribute>() != null;
        }

        /// <summary>
        /// 获取一个值，表示指定字段是否将被序列化为MapValue。
        /// </summary>
        protected static bool IsDataMapValue(FieldInfo fieldInfo)
        {
            if (fieldInfo == null)
                return false;
            return fieldInfo.GetCustomAttribute<DataMapValueAttribute>() != null;
        }

        /// <summary>
        /// 获取一个值，表示指定的类型是否为基础类型。
        /// </summary>
        /// <param name="elementType">需要检测的类型</param>
        protected static bool IsPrimitiveType(Type elementType)
        {
            return mPrimitiveTypes.Contains(elementType);
        }

        /// <summary>
        /// 将指定的文本进行转义。
        /// </summary>
        /// <param name="input">输入文本</param>
        protected virtual string Escape(string input)
        {
            var buffer = new StringBuilder();
            for (var i = 0; i < input.Length; i++)
            {
                var c = input[i];
                switch (c)
                {
                    case '\\':
                        buffer.Append("\\\\");
                        break;
                    case '\"':
                        buffer.Append("\\\"");
                        break;
                    case '\r':
                        buffer.Append("\\r");
                        break;
                    case '\n':
                        buffer.Append("\\n");
                        break;
                    default:
                        buffer.Append(c);
                        break;
                }
            }
            return buffer.ToString();
        }

        /// <summary>
        /// 当需要序列化时调用此方法。
        /// </summary>
        /// <param name="target">需要序列化的对象</param>
        protected abstract void OnSerialize(object target);

        protected void WriteFieldValue(FieldInfo fieldInfo, object value)
        {
            if (value == null)
                return;
            var type = fieldInfo.FieldType;
            Write(type, value);
        }

        private static IEnumerable<FieldInfo> getFieldsToWrite(Type type, object instance)
        {
            foreach (var fieldInfo in type.GetFields(BindingFlags.Instance | BindingFlags.Public))
            {
                var value = fieldInfo.GetValue(instance);
                if (value == null)
                {
                    //值为空：不输出
                    continue;
                }

                var luaIgnore = fieldInfo.GetCustomAttribute<DataIgnoreAttribute>();
                if (luaIgnore != null)
                    continue;

                var luaFieldAttribute = fieldInfo.GetCustomAttribute<DataFieldAttribute>();
                if (luaFieldAttribute != null)
                {
                    if (luaFieldAttribute.DefaultValue != null
                        && luaFieldAttribute.DefaultValue.Equals(value))
                    {
                        //和默认值相同，不输出
                        continue;
                    }
                }

                //字典的键不输出
                var luaMapKeyAttribute = fieldInfo.GetCustomAttribute<DataMapKeyAttribute>();
                if (luaMapKeyAttribute != null)
                    continue;
                yield return fieldInfo;
            }
        }

        private static bool isIgnored(FieldInfo fieldInfo)
        {
            return fieldInfo.GetCustomAttribute<DataIgnoreAttribute>() != null;
        }

        private static int sortField(FieldInfo x, FieldInfo y)
        {
            var xAttribute = x.GetCustomAttribute<DataFieldAttribute>();
            var yAttribute = y.GetCustomAttribute<DataFieldAttribute>();

            var xName = x.Name;
            var yName = y.Name;
            var xOrder = 0;
            var yOrder = 0;
            if (xAttribute != null)
            {
                if (xAttribute.Name != null)
                    xName = xAttribute.Name;
                xOrder = xAttribute.Order;
            }
            if (yAttribute != null)
            {
                if (yAttribute.Name != null)
                    yName = yAttribute.Name;
                yOrder = yAttribute.Order;
            }
            if (xOrder == yOrder)
                return string.Compare(xName, yName, StringComparison.Ordinal);
            return xOrder.CompareTo(yOrder);
        }

        private bool writeArray(string fieldName, object arrayObject, bool shrink = false)
        {
            if (arrayObject == null)
                return false;

            var array = (Array) arrayObject;
            var elementType = arrayObject.GetType().GetElementType();

            if (fieldName != null)
            {
                WriteFieldName(fieldName);
                WriteAssignmentSymbol();
            }

            using (GetCollectionBlock(!shrink))
            {
                for (var i = 0; i < array.Length; i++)
                {
                    Write(elementType, array.GetValue(i));
                    if (i < array.Length - 1)
                        Writer.Write(",");
                }
            }
            return true;
        }

        private bool writeCollection(string fieldName, object collectionObject, Type elementType, bool shrink = false)
        {
            if (collectionObject == null)
                return false;

            var array = ((ICollection) collectionObject).Cast<object>().ToArray();
            if (elementType == null)
                elementType = typeof (object);

            if (fieldName != null)
            {
                WriteFieldName(fieldName);
                WriteAssignmentSymbol();
            }

            using (GetCollectionBlock(!shrink))
            {
                for (var i = 0; i < array.Length; i++)
                {
                    Write(elementType, array.GetValue(i));
                    if (i < array.Length - 1)
                        Writer.Write(",");
                }
            }

            if (!shrink)
            {
                Writer.WriteLine();
                WriteIndentation(mIndentLevel);
            }
            return true;
        }

        private bool writeDataMap(object instance)
        {
            if (instance == null)
                return false;

            var type = instance.GetType();
            Array array;
            if (type.IsArray)
                array = (Array) instance;
            else
                array = ((ICollection) instance).Cast<object>().ToArray();

            var ret = false;
            for (var i = 0; i < array.Length; i++)
            {
                if (writeMapItem(array, i))
                    ret = true;
                if (i < array.Length - 1)
                {
                    WriteLine(",");
                    WriteIndentation(mIndentLevel);
                }
            }
            return ret;
        }

        private bool writeField(object instance, FieldInfo fieldInfo)
        {
            var value = fieldInfo.GetValue(instance);
            if (value == null)
            {
                //值为空：不输出
                return false;
            }

            var fieldName = fieldInfo.Name;

            var luaIgnore = fieldInfo.GetCustomAttribute<DataIgnoreAttribute>();
            if (luaIgnore != null)
                return false;

            var luaFieldAttribute = fieldInfo.GetCustomAttribute<DataFieldAttribute>();
            if (luaFieldAttribute != null)
            {
                if (luaFieldAttribute.Name != null)
                    fieldName = luaFieldAttribute.Name;

                if (luaFieldAttribute.DefaultValue != null
                    && luaFieldAttribute.DefaultValue.Equals(value))
                {
                    //和默认值相同，不输出
                    return false;
                }
            }

            var fieldType = fieldInfo.FieldType;
            if (IsDataMap(fieldInfo))
            {
                writeDataMap(value);
                return true;
            }

            if (fieldType.IsArray)
            {
                var elementType = fieldType.GetElementType();
                var shrink = IsPrimitiveType(elementType);
                writeArray(fieldName, value, shrink);
                return true;
            }

            if (typeof (ICollection).IsAssignableFrom(fieldType))
            {
                var elementType = fieldType.IsGenericType ? fieldType.GetGenericArguments()[0] : fieldType.GetElementType();
                var shrink = IsPrimitiveType(elementType);
                writeCollection(fieldName, value, elementType, shrink);
                return true;
            }

            var luaMapAttribute = fieldType.GetCustomAttribute<DataMapAttribute>();
            if (luaMapAttribute != null)
            {
                Write(value.GetType(), value);
                return true;
            }

            WriteFieldName(fieldName);
            WriteAssignmentSymbol();
            WriteFieldValue(fieldInfo, value);
            return true;
        }

        private bool writeMapItem(Array map, int index)
        {
            var mapItem = map.GetValue(index);
            if (mapItem == null)
                return false;

            var type = mapItem.GetType();

            var fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.Public).ToList();
            fieldInfos.Sort(sortField);

            var keyFieldInfo = fieldInfos.Single(f => f.GetCustomAttribute<DataMapKeyAttribute>() != null);
            var valueFieldInfo = fieldInfos.Single(f => f.GetCustomAttribute<DataMapValueAttribute>() != null);

            //输出Map字段
            WriteMapKey(mapItem, keyFieldInfo);
            WriteAssignmentSymbol();

            //嵌套的Map多加一个{}
            if (IsDataMap(valueFieldInfo))
            {
                using (GetMapBlock(true))
                    writeMapItemValue(mapItem);
            }
            else
                writeMapItemValue(mapItem);
            return true;
        }

        private void writeMapItemValue(object mapItem)
        {
            if (mapItem == null)
                return;

            var type = mapItem.GetType();
            var fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.Public).Where(fieldInfo => !isIgnored(fieldInfo)).ToList();
            fieldInfos.Sort(sortField);

            var keyFieldInfo = fieldInfos.Single(fieldInfo => fieldInfo.GetCustomAttribute<DataMapKeyAttribute>() != null);
            var valueFieldInfo = fieldInfos.Single(fieldInfo => fieldInfo.GetCustomAttribute<DataMapValueAttribute>() != null);


            //输出所有非Map字段
            var writableFieldCount = fieldInfos.Count - 1; //key和value只算一个，若值不存在则都不输出
            if (valueFieldInfo.GetValue(mapItem) == null)
                writableFieldCount--;
            for (var index = 0; index < fieldInfos.Count; index++)
            {
                var fieldInfo = fieldInfos[index];
                if (fieldInfo == keyFieldInfo || fieldInfo == valueFieldInfo)
                    continue;
                if (writeField(mapItem, fieldInfo) && index < writableFieldCount - 1)
                {
                    WriteLine(",");
                    WriteIndentation(mIndentLevel);
                }
            }

            var valueFieldValue = valueFieldInfo.GetValue(mapItem);
            if (valueFieldInfo.FieldType == typeof (object) && valueFieldValue != null)
            {
                Write(valueFieldValue.GetType(), valueFieldValue, valueFieldInfo);
            }
            else
            {
                Write(valueFieldInfo.FieldType, valueFieldValue, valueFieldInfo);
            }
        }

        private static readonly Type[] mPrimitiveTypes =
        {
            typeof (char),
            typeof (string),
            typeof (int),
            typeof (double),
            typeof (float),
            typeof (bool),
        };

        private int mIndentLevel;

        #region Writing

        /// <summary>
        /// 写入类型为<see cref="string"/>的字段的值。
        /// </summary>
        /// <param name="value">字段的值</param>
        protected virtual void WriteFieldValue(string value)
        {
            Writer.Write("\"{0}\"", Escape(Convert.ToString(value)));
        }

        /// <summary>
        /// 写入类型为<see cref="char"/>的字段的值。
        /// </summary>
        /// <param name="value">字段的值</param>
        protected virtual void WriteFieldValue(char value)
        {
            Writer.Write("\"{0}\"", Escape(Convert.ToString(value)));
        }

        /// <summary>
        /// 写入类型为<see cref="int"/>的字段的值。
        /// </summary>
        /// <param name="value">字段的值</param>
        protected virtual void WriteFieldValue(int value)
        {
            Writer.Write(value);
        }

        /// <summary>
        /// 写入类型为<see cref="double"/>的字段的值。
        /// </summary>
        /// <param name="value">字段的值</param>
        protected virtual void WriteFieldValue(double value)
        {
            Writer.Write(value);
        }

        /// <summary>
        /// 写入类型为<see cref="float"/>的字段的值。
        /// </summary>
        /// <param name="value">字段的值</param>
        protected virtual void WriteFieldValue(float value)
        {
            Writer.Write(value);
        }

        /// <summary>
        /// 写入类型为<see cref="bool"/>的字段的值。
        /// </summary>
        /// <param name="value">字段的值</param>
        protected virtual void WriteFieldValue(bool value)
        {
            if (value)
                Writer.Write("true");
            else
                Writer.Write("false");
        }

        /// <summary>
        /// 写入简单类型的字段。
        /// </summary>
        /// <param name="fieldType">字段类型</param>
        /// <param name="fieldValue">字段的值</param>
        protected bool WritePrimitiveField(Type fieldType, object fieldValue)
        {
            if (fieldType == typeof (char))
            {
                WriteFieldValue((char) fieldValue);
                return true;
            }

            if (fieldType == typeof (string))
            {
                WriteFieldValue((string) fieldValue);
                return true;
            }

            if (fieldType == typeof (int))
            {
                WriteFieldValue((int) fieldValue);
                return true;
            }

            if (fieldType == typeof (double))
            {
                WriteFieldValue((double) fieldValue);
                return true;
            }

            if (fieldType == typeof (float))
            {
                WriteFieldValue((float) fieldValue);
                return true;
            }

            if (fieldType == typeof (bool))
            {
                WriteFieldValue((bool) fieldValue);
                return true;
            }

            throw new ArgumentException("undefined primitive type -> " + fieldType.Name);
        }

        /// <summary>
        /// 获取数组数据块。
        /// </summary>
        protected virtual Block GetCollectionBlock(bool newLine)
        {
            return new Block(this, "[", "]", newLine);
        }

        /// <summary>
        /// 获取字典数据块。
        /// </summary>
        protected virtual Block GetMapBlock(bool newLine)
        {
            return new Block(this, "{", "}", newLine);
        }

        /// <summary>
        /// 写入Map的键。
        /// </summary>
        protected virtual void WriteMapKey(object instance, FieldInfo fieldInfo)
        {
            Write("[");
            var value = fieldInfo.GetValue(instance);
            WritePrimitiveField(fieldInfo.FieldType, value);
            Write("]");
        }

        /// <summary>
        /// 写入字符串文本。
        /// </summary>
        /// <param name="text">需要写入的文本内容</param>
        protected virtual void Write(string text)
        {
            Writer.Write(text);
        }

        protected bool Write(Type type, object value, FieldInfo fieldInfo = null)
        {
            if (IsDataMap(fieldInfo))
            {
                return writeDataMap(value);
            }

            if (fieldInfo != null && !IsDataMapKey(fieldInfo) && !IsDataMapValue(fieldInfo))
            {
                WriteFieldName(fieldInfo.Name);
                WriteAssignmentSymbol();
            }

            if (IsPrimitiveType(type))
            {
                return WritePrimitiveField(type, value);
            }

            if (type.IsArray)
            {
                var elementType = type.GetElementType();
                var shrink = IsPrimitiveType(elementType);
                return writeArray(null, value, shrink);
            }

            if (typeof (ICollection).IsAssignableFrom(type))
            {
                var elementType = type.IsGenericType ? type.GetGenericArguments()[0] : type.GetElementType();
                return writeCollection(null, value, elementType);
            }

            //输出所有字段
            var fieldInfos = getFieldsToWrite(type, value).ToList();
            fieldInfos.Sort(sortField);
            using (GetMapBlock(true))
            {
                for (var index = 0; index < fieldInfos.Count; index++)
                {
                    var subFieldInfo = fieldInfos[index];
                    var subFieldValue = subFieldInfo.GetValue(value);
                    var subFieldType = subFieldValue == null ? subFieldInfo.FieldType : subFieldValue.GetType();
                    if (Write(subFieldType, subFieldValue, subFieldInfo) && index < fieldInfos.Count - 1)
                    {
                        WriteLine(",");
                        WriteIndentation(mIndentLevel);
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// 写入一行文本，并在末尾追加换行符。
        /// </summary>
        /// <param name="text">需要写入的文本内容</param>
        protected virtual void WriteLine(string text = null)
        {
            if (text == null)
                Writer.WriteLine();
            else
                Writer.WriteLine(text);
        }

        /// <summary>
        /// 写入赋值符号。 如：在lua中为"="，在python中为":"。
        /// </summary>
        protected virtual void WriteAssignmentSymbol()
        {
            Writer.Write(" = ");
        }

        /// <summary>
        /// 写入字段名。
        /// </summary>
        protected virtual void WriteFieldName(string fieldName)
        {
            Writer.Write(fieldName);
        }

        /// <summary>
        /// 写入缩进符号。
        /// </summary>
        /// <param name="level">缩进等级</param>
        protected virtual void WriteIndentation(int level)
        {
            for (var i = 0; i < level; i++)
                Write("    ");
        }

        #endregion
    }
}